#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <ctype.h>

#include <strhelper.h>

void str_rtrim(char *p)
{
    int i = strlen (p);

    while (--i >= 0 && CHAR_IS_WS(p[i]))
        p[i] = 0;
}

void str_ltrim(char *p)
{
    char *b = p;

    SKIP_HEADING_SPACE(p);

    if (p > b)
        memmove(b, p, strlen(p) + 1);
}

void str_lrtrim(char *p)
{
    str_ltrim(p);
    str_rtrim(p);
}

void str_rtrim(std::string& s)
{
    int i;

    if ((i = s.size()) > 0) {
        while (--i >= 0 && CHAR_IS_WS(s[i]))
            ;
        ++i;
        if (i < (int) s.size())
            s.resize(i);
    }
}

void str_ltrim(std::string& s)
{
    const char *p = s.c_str();

    SKIP_HEADING_SPACE(p);

    if (p > s.c_str()) {
        std::string tmp(p);
        s = tmp;
    }
}

void str_lrtrim(std::string& s)
{
    str_ltrim(s);
    str_rtrim(s);
}

bool
str_begin_with_no_case(const char *str,
                       const char *cmp)
{
    char c1, c2;

    for (;;) {
        c1 = *str;
        c2 = *cmp;
        if (c1 && c2
            && (CHAR_LOWER(c1) == CHAR_LOWER(c2))) {
            ++str;
            ++cmp;
        }
        else
            break;
    }
    return c2 == 0;
}

void *
str_find_char_no_case(const char *str,
                      char        c)
{
    char lc = CHAR_LOWER(c);
    while (*str) {
        if (CHAR_LOWER(*str) == lc)
            return (void *) str;
        ++str;
    }
    return NULL;
}

int
str_find_no_case(const char *str,
                 const char *cmp)
{
    char        first_char;
    const char *p;

    p          = str;
    first_char = *cmp;

    for (;;) {
        p = (const char *) str_find_char_no_case (p, first_char);
        if (p) {
            if (str_begin_with_no_case(p, cmp))
                return p - str;
            else
                ++p;
        }
        else
            break;
    }
    return -1;
}

char
str_next_char_no_space(const char *str)
{
    while (*str) {
        if (!CHAR_IS_WS(*str))
            return *str;
        ++str;
    }
    return 0;
}

char *
str_next_char_except(const char *p, const char *exps)
{
    char c;

    while ((c = *p)) {
        if (strchr(exps, c))
            ++p;
        else
            break;
    }

    return (char *) p;
}

char *
str_find_char_unless(const char *p, char c, const char *exps)
{
    char tmp;

    while ((tmp = *p)) {
        if (c == tmp)
            return (char *) p;

        if (strchr(exps, tmp))
            break;
        else
            ++p;
    }

    return NULL;
}

char *
str_get_fn_nondir(const char *path)
{
#ifdef _WIN32
    char sep = '\\';
#else
    char sep = '/';
#endif

    char *pos = strrchr(path, sep);


    return (pos == NULL) ? strdup(path) : strdup(pos + 1);
}

int
str_get_str_until(char  *p,   char spec,
                  char  *buf, int bufsiz,
                  char **end)
{
    char c;
    int  i        = 0;
    bool too_long = false;

    while ((c = *p) != 0 && c != spec) {
        if (i + 1 >= bufsiz) {
            too_long = true;
            break;
        }

        buf[i++] = c;

        ++p;
    }

    buf[i] = 0;

    *end = p;

    return too_long ? -1 : 0;
}

int
str_get_str_until(char *p, char spec,
                  std::string& r, char **end)
{
    char c;

    r.clear();

    while ((c = *p) != 0 && c != spec) {
        r.push_back(c);
        ++p;
    }

    *end = p;

    return 0;
}

int
str_get_escape_str_until(char  *p,   char spec,
                         char  *buf, int  bufsiz,
                         char **end)
{
    char c;
    int  i        = 0;
    bool too_long = false;

    while ((c = *p) != 0 && c != spec) {
        if (i + 1 >= bufsiz) {
            too_long = true;
            break;
        }

        switch (c) {
        default:
            buf[i++] = c;
            break;

        case '\\':
            c = *++p;
            if (c)
                buf[i++] = c;
            break;
        }

        ++p;
    }

    buf[i] = 0;

    *end = p;

    return too_long ? -1 : 0;
}

int
str_get_escape_str_until(char  *p, char spec,
                         std::string& r, char **end)
{
    char c;

    r.clear();

    while ((c = *p) != 0 && c != spec) {
        switch (c) {
        default:
            r.push_back(c);
            break;

        case '\\':
            c = *++p;
            if (c)
                r.push_back(c);
            break;
        }

        ++p;
    }

    *end = p;

    return 0;
}

void
str_get_suffix(const char *s, std::string& suffix)
{
    const char *p = strrchr(s, '.');

    if (p)
        suffix.assign(p + 1);
    else
        suffix.clear();
}

void
str_lower(std::string& s)
{
    int len = (int) s.size();
    int i;
    for (i = 0; i < len; i++) {
        if (CHAR_IS_UPPER(s[i]))
            s[i] = s[i] - 'A' + 'a';
    }
}

void
str_upper(std::string& s)
{
    int len = (int) s.size();
    int i;
    for (i = 0; i < len; i++) {
        if (CHAR_IS_LOWER(s[i]))
            s[i] = s[i] - 'a' + 'A';
    }
}

void
str_lower(char *s)
{
    int i;
    for (i = 0; s[i]; i++) {
        if (CHAR_IS_UPPER(s[i]))
            s[i] = s[i] - 'A' + 'a';
    }
}

void
str_upper(char *s)
{
    int i;
    for (i = 0; s[i]; i++) {
        if (CHAR_IS_LOWER(s[i]))
            s[i] = s[i] - 'a' + 'A';
    }
}

bool
str_suffix_is(const char *s, const char *suffix)
{
    std::string s1;

    str_get_suffix(s, s1);

    return strcasecmp(s1.c_str(), suffix) == 0;
}

void
str_append(std::string& r, int i)
{
    char buf[32];
    sprintf(buf, "%d", i);
    r += buf;
}

#define _GET_FORMAT_STRING(N)                                           \
    int      _fmt_buffer_size = 0;                                      \
    char    *_fmt_buffer      = NULL;                                   \
    do {                                                                \
        int      n;                                                     \
        va_list  ap;                                                    \
        char    *np;                                                    \
                                                                        \
        if (_fmt_buffer == NULL) {                                      \
            _fmt_buffer_size = 4096;                                    \
            _fmt_buffer = (char *) malloc (_fmt_buffer_size);           \
                                                                        \
            if (_fmt_buffer == NULL) {                                  \
                N = -1;                                                 \
                break;                                                  \
            }                                                           \
        }                                                               \
                                                                        \
        for (;;) {                                                      \
            va_start(ap, fmt);                                          \
            n = vsnprintf(_fmt_buffer, _fmt_buffer_size, fmt, ap);      \
            va_end(ap);                                                 \
                                                                        \
            if (n > -1 && n < _fmt_buffer_size) {                       \
                N = n;                                                  \
                break;                                                  \
            }                                                           \
                                                                        \
            _fmt_buffer_size *= 2;                                      \
            if ((np = (char *) realloc (_fmt_buffer,                    \
                                        _fmt_buffer_size)) == NULL) {   \
                free(_fmt_buffer);                                      \
                _fmt_buffer = NULL;                                     \
                N = -1;                                                 \
                break;                                                  \
            }                                                           \
            else {                                                      \
                _fmt_buffer = np;                                       \
            }                                                           \
        }                                                               \
    } while (0)

#define _FREE_FORMAT_BUFFER()                   \
    do {                                        \
        if (_fmt_buffer) {                      \
            free(_fmt_buffer);                  \
            _fmt_buffer = NULL;                 \
            _fmt_buffer_size = 0;               \
        }                                       \
    } while (0)

int
str_format(std::string& r, const char *fmt, ...)
{
    int size;

    _GET_FORMAT_STRING(size);

    if (size < 0)
        return -1;

    r = _fmt_buffer;

    _FREE_FORMAT_BUFFER();

    return (int) r.size();
}

int
str_format_append(std::string& r, const char *fmt, ...)
{
    int size;

    _GET_FORMAT_STRING(size);

    if (size < 0)
        return -1;

    r += _fmt_buffer;

    _FREE_FORMAT_BUFFER();

    return (int) r.size();
}

int
str_parse_int(char *p, int *r, char **end)
{
    int  cnt = 0;
    int  rt  = 0;
    char c;

    while ((c = *p) && (CHAR_IS_DIG(c))) {
        cnt *= 10;
        cnt += c - '0';
        if (cnt < 0) {
            rt = -1;
            break;
        }
        ++p;
    }

    *r = cnt;

    if (end)
        *end = p;

    return rt;
}

void
str_no_suffix(const char *name, std::string& r)
{
    int i;

    r.clear();

    i = strlen (name);

    if (i > 0) {
        --i;
        while (i >= 0 && name[i] != '.')
            --i;

        if (i < 0)
            r = name;
        else {
            r.assign(name, i);
        }
    }
}

int
str_get_char_cnt(const char *str, char c)
{
    int  cnt;
    char tmp;

    cnt = 0;
    while ((tmp = *str++) != 0)
        if (tmp == c)
            ++cnt;

    return cnt;
}

int
str_get_char_cnt(const char *str, int len, char c)
{
    int cnt, i;

    cnt = 0;
    for (i = 0; i < len; i++) {
        if (str[i] == c)
            ++cnt;
    }

    return cnt;
}

static char _hex_table[] =
{
    '0', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
};

#define CHAR_TO_INT(c)                          \
    ((int) ((unsigned char) (c)))

static char _hex_pred_table[256];

static bool _hex_pred_table_init_p = false;

static inline void
_init_hex_pred_table()
{
    int i;

    if (!_hex_pred_table_init_p) {
        _hex_pred_table_init_p = true;

        memset(_hex_pred_table, 0, 256);

        for (i = 0; i < 256; i++) {
            if (i >= '0' && i <= '9')
                _hex_pred_table[i] = i - '0' + 1;
            else if (i >= 'A' && i <= 'F')
                _hex_pred_table[i] = i - 'A' + 11;
            else if (i >= 'a' && i <= 'f')
                _hex_pred_table[i] = i - 'a' + 11;
        }
    }
}

bool
str_is_all_hex(const char *str, int len)
{
    int i;

    _init_hex_pred_table();

    for (i = 0; i < len; i++) {
        if (!_hex_pred_table[CHAR_TO_INT(str[i])])
            return false;
    }

    return true;
}

// return the length of out buffer
// don't adding the trailing zero-terminal
int
str_hex_to_bytes_not_check(const char *str, int len, char *out)
{
    int   i;
    char *p;

    _init_hex_pred_table();

    p = out;

    for (i = 0; i < len; i += 2) {
        *p++ = (((_hex_pred_table[CHAR_TO_INT(CHAR_UPPER(str[i]))] - 1) << 4)
                | (_hex_pred_table[CHAR_TO_INT((i + 1 < len) ? str[i + 1] : '0')] - 1));
    }

    return (int) (p - out);
}

void
str_to_hex(const void *p, int len, std::string& res)
{
    res.clear();

    if (len <= 0)
        return;

    res.reserve(len * 4);

    char b[4];
    b[3] = 0;

    int i;
    for (i = 0; i < len; i++) {
        unsigned char c = ((const unsigned char *) p)[i];
        b[0] = _hex_table[(c >> 4)];
        b[1] = _hex_table[c & 0x0f];
        if (i + 1 < len)
            b[2] = ' ';
        else
            b[2] = 0;
        res += b;

        if ((i + 1) < len && ((i + 1) % 20) == 0)
            res += '\n';
    }
}

void str_to_printable(const void   *p,
                      int           len,
                      std::string&  res,
                      char          replace)
{
    res.clear();

    if (len <= 0)
        return;

    int i;
    for (i = 0; i < len; i++) {
        int c = ((const unsigned char *) p)[i];

        if (isprint(c))
            res.push_back((char) c);
        else
            res.push_back(replace);
    }
}

void str_to_printable_hex(const void   *p,
                          int           len,
                          std::string&  res)
{
    res.clear();

    if (len <= 0)
        return;

    char b[8];
    b[0] = '[';
    b[3] = ']';
    b[4] = 0;

    int i;
    for (i = 0; i < len; i++) {
        int c = ((const unsigned char *) p)[i];

        if (isprint(c))
            res.push_back((char) c);
        else {
            b[1] = _hex_table[(c >> 4)];
            b[2] = _hex_table[c & 0x0f];
            res += b;
        }
    }
}

char *
str_dup(const char *str, int len)
{
    if (len <= 0)
        len = strlen(str);

    char *dst = (char *) malloc (len + 1);

    memcpy(dst, str, len);

    dst[len] = 0;

    return dst;
}

void
str_replace_char(char *str, char from, char to)
{
    int i;

    for (i = 0; str[i]; i++) {
        if (str[i] == from)
            str[i] = to;
    }
}

int
str_split_by_chars(const char                *src,
                   const char                *seps,
                   std::vector<std::string>&  res)
{
    char        c;
    std::string str;

    for (;;) {
        c = *src++;

        if (c == 0 || strchr((char *) seps, c)) {
            if (str.size() > 0) {
                res.push_back(str);
                str.clear();
            }

            if (c) {
                while ((c = *src++) != 0) {
                    if (!strchr((char *) seps, c))
                        break;
                }

                if (c)
                    str.push_back(c);
            }

            if (!c)
                break;
        }
        else
            str.push_back(c);
    }

    return (int) res.size();
}
